﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Kokomo.IO
{
	public class MappedStream : Stream
	{
		public Stream BaseStream { get; private set; }
		public IEnumerable<StreamRegion> Regions { get; private set; }

		public MappedStream(Stream baseStream, IEnumerable<StreamRegion> regions)
		{
			ArgumentValidation.CheckNullArgument("baseStream", baseStream);
			ArgumentValidation.CheckNullArgument("regions", regions);
			this.BaseStream = baseStream;
			this.Regions = regions;
			this.Position = 0;
		}
		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);
			this.BaseStream.Dispose();
		}

		public override bool CanRead
		{
			get { return true; }
		}

		public override bool CanSeek
		{
			get { return true; }
		}

		public override bool CanWrite
		{
			get { return true; }
		}

		public override void Flush()
		{
		}

		public override long Length
		{
			get
			{
				long maxAddress = 0;
				foreach (var region in this.Regions)
				{
					if (region.Length > 0)
					{
						long endAddress = region.Offset + region.Length;
						maxAddress = Math.Max(maxAddress, endAddress);
					}
				}
				return maxAddress;
			}
		}

		private long _position;
		public StreamRegion CurrentRegion { get; private set; }
		public long SectionOffset { get; private set; }
		public override long Position
		{
			get
			{
				return this._position;
			}
			set
			{
				// Search for the section containing the address
				StreamRegion targetRegion = null;
				foreach (var region in this.Regions)
				{
					if (region.Contains(value) || ((region.Offset > value) && ((targetRegion == null) || (targetRegion.Offset > region.Offset))))
					{
						targetRegion = region;
					}
				}

				if (targetRegion != null)
				{
					this.CurrentRegion = targetRegion;
					this.SectionOffset = value - targetRegion.Offset;
					if (this.SectionOffset >= 0)
					{
						this.BaseStream.Position = this.CurrentRegion.BaseOffset + this.SectionOffset;
					}
				}
				this._position = value;
			}
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			int bytesRead = 0;
			while (count > 0)
			{
				if (this.CurrentRegion == null)
				{
					break;
				}

				int blockSize;
				if (this.SectionOffset > 0)
				{
					blockSize = (int)Math.Min(count, this.CurrentRegion.Length - this.SectionOffset);
					blockSize = this.BaseStream.Read(buffer, bytesRead, blockSize);
					//Array.Copy(this.CurrentRegion, this.SectionOffset, buffer, bytesRead, blockSize);
				}
				else
				{
					blockSize = (int)Math.Min(count, -this.SectionOffset);
					Array.Clear(buffer, offset, blockSize);
				}
				this.Position += blockSize;
				bytesRead += blockSize;
				count -= blockSize;
			}

			return bytesRead;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotImplementedException();
		}

		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			int bytesRead = 0;
			while (count > 0)
			{
				if (this.CurrentRegion == null)
				{
					break;
				}

				int blockSize;
				if (this.SectionOffset > 0)
				{
					blockSize = (int)Math.Min(count, this.CurrentRegion.Length - this.SectionOffset);
					this.BaseStream.Write(buffer, bytesRead, blockSize);
					//Array.Copy(this.CurrentRegion, this.SectionOffset, buffer, bytesRead, blockSize);
				}
				else
				{
					blockSize = (int)Math.Min(count, -this.SectionOffset);
					//Array.Clear(buffer, offset, blockSize);
				}
				this.Position += blockSize;
				bytesRead += blockSize;
				count -= blockSize;
			}
		}
	}
}
